package mya_dc.shared_classes;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;

import mya_dc.shared_classes.Message.EMessageType;
import mya_dc.shared_classes.files_manipulation.FileObject;
import mya_dc.shared_classes.files_manipulation.ZipManager;

/**
 * Allows sending object and files through a given socket.
 *
 * <p>
 * <b>Notes:</b>
 * <ul>
 * <li>Transmitter only sends messages of type Message.
 * <li>Files sent will be zipped via the class ZipManager.
 * <li>All objects referenced by a given message must implement Serializable.
 * <li>Transmitter saves files in their relative path in the following way:
 * <br>suppose WorkingDirectory in the sender's side is c:\sen\
 * <br>suppose WorkingDirectory in the receiver's side is c:\rec\
 * <br>suppose the sender sends the file located at c:\sen\dir1\dir2\file.txt
 * <br> than at the receiver's side the path c:\rec\dir1\dir2
 * <br> will be created and the file will be saved at c:\sen\dir1\dir2\file.txt
 * <li>Working directory can be updated.
 * <li>Working directory (for now) must end with "\" for example:
 * <br>Working directory = "c:\\tmp\\"; 
 * </ul>
 * <p>
 * 
 * @author      Adam Levi
 * <br>			MYA
 */

public class Transmitter
{
	/**
	 * Constructor
	 *
	 * <br>
	 * <br>
	 * <b>Notes: </b>
	 * </br>
	 * <li>no files should be sent throw the transmitter until setWorkingDirectory() is called
	 * <li> you cannot use the option keepAlive and receive timeout
	 * 		exception. use keepAlive if you wish to keep the
	 * 		connection alive. use timeout with keepAlive set to false
	 * 		if you wish the transmitter to wait for 'timeOut' time
	 * 		while waiting for an object to be received.
	 * 		
	 *</br>
	 * 
	 * </br>
	 * @param 	socket - a socket
	 * @param	workingDirectory - the working directory
	 * @param	secBetweenTwoKeepAliveMessages - the number of seconds
	 * 			between two keep alive messages.
	 * 
	 */
	public Transmitter(
			Socket socket,
			String workingDirectory,
			int secBetweenTwoKeepAliveMessages)
	{
		setWorkingDirectory(workingDirectory);
		setTimeOutSec(secBetweenTwoKeepAliveMessages);
		setSocket(socket);
		setKeepAlive(true);
	}
	
	/**
 	 * sets the new status of keepAlive
	 */
	public void setKeepAlive(boolean keepAlive)
	{
		m_KeepAlive = keepAlive;
	}
	
	/**
	* updates the working directory.
	*
	* @param workingDirectory - the new working directory.
	*/
	public void setWorkingDirectory(String workingDirectory)
	{
		if (workingDirectory == null) return;
		if (workingDirectory.charAt(workingDirectory.length() - 1) != '\\')
		{
			m_WorkingDirectory = workingDirectory + "\\";
		}
		else
		{
			m_WorkingDirectory = workingDirectory;	
		}
		File fPath = new File(m_WorkingDirectory);
		if (!fPath.exists())
		{
			fPath.mkdirs();
		}
	}
	
	/**
	 * Returns the current workingDirectory of the Transmitter
	 *
	 * @return      m_WorkingDirectory
	 */
	public String getWorkingDirectory()
	{
		return m_WorkingDirectory;
	}
	
	/**
 	 * sets a new timeout for receiving / seconds
	 */
	public void setTimeOutSec(int timeout)
	{
		m_SocketTimeoutSec = timeout;
	}
	
	
	
	/**
	*	Sends a message via to the remote host.
	* <ul>
	* <li>If message.getType() is of type File then the message.getObject() will
	*		be sent completely and will be saved in the appropriate relative path at the
	*		receiver's side.
	* <li>If message.getType() is of type File[] then all the files will be transmitted
	* 		to the remote host.
	*		receiver's side.
	* <li>In any other case the given object will be transmitted as is.
	* </ul>
	*
	* @param	Message - message to Send.
	*/
	public void send(Message message) throws IOException
	{
		sendAux(message);
	}
	
	/**
	*	receives a message via from the remote host.
	* <ul>
	* <li>	If the message.getType() is of type FileObject then the file will be saved in 
	*		the appropriate relative path and a pointer of type File will be set in the
	*		message that will be returned.
	* <li>	If message.getType() is of type File[] then all the files will be transmitted
	* 		to the remote host.
	* <li>	If message.getType() is not of type File then the given object will be returned
	*		(casting required).
	* </ul>
	* 
	* @return a message according to the rules above
	*
	*/
	public Message receive() throws IOException, ClassNotFoundException
	{
		return receiveAux();
	}
	
	/**
	*	closes the transmitter' socket
	*/
	public void close()
	{
		try { m_Socket.close(); }
		catch (IOException e) { }
	}
	
	/**
	*	@return true if the transmitter is connected to the remote host, otherwise false
	*/
	public boolean isConnected()
	{
		if (m_Socket == null)
			return false;
		{
			try {
				send(new Message(EMessageType.TRANSMITTER_KEEPALIVE_MSG,null));
			} catch (IOException e) {
				this.close();
				return false;
			}
			
		}
		return true;
	}
	
	//variables
	private String 		m_WorkingDirectory;
	private Socket 		m_Socket;
	private boolean 	m_KeepAlive = false;
	private int			m_SocketTimeoutSec = 0; 
	
	
	/**
	*	sets the transmitter's socket to socket and then 
	*/
	private void setSocket(Socket socket)
	{
		m_Socket = socket;
		if (m_Socket != null)
		{
			try { m_Socket.setSoTimeout(m_SocketTimeoutSec * 1000); }
			catch (SocketException e)  { }
		}
	}
	
	/**
	*	an auxiliary method that sends the message.
	*
	* 	@param	Message - message to send.
	* 
	* 	@see	send()
	*/
	private void sendAux(Message message) throws IOException
	{
		/* checks if message contains a file */
		Object tmpObj = message.getObject();
		if (tmpObj instanceof File)
		{
			sendFile((File)tmpObj, message.getType());
		}
		else if (tmpObj instanceof File[])
		{
			objectToSocket(
					new Message(EMessageType.TRANSMITTER_PACK_OF_FILES_START, message.getType()));
			for (File f : (File[])tmpObj)
				sendFile(f,EMessageType.TRANSMITTER_PACK_OF_FILES_FILE_TRANSFER);
			objectToSocket(
					new Message(EMessageType.TRANSMITTER_PACK_OF_FILES_DONE, null));
		}
		else
			objectToSocket(message);
	}
	
	/**
	*	an auxiliary method that receives a message.
	*
	* 	@see	receive()
	*/
	private Message receiveAux() throws IOException, ClassNotFoundException
	{
		boolean lastMsgWasKeepAlive = true;
		ObjectInputStream inStream = null;
		Message message = null;
		while(lastMsgWasKeepAlive)
		{
			if (m_KeepAlive) //check liveness while receiving
			{
				while(message == null)
				{
					try
					{
						inStream = new ObjectInputStream(m_Socket.getInputStream());
						message = (Message)inStream.readObject();
					}
					catch(SocketTimeoutException e)
					{
						//check for liveness
						sendKeepAlive();
					}
				}
			}
			else
			{
				inStream = new ObjectInputStream(m_Socket.getInputStream());
				message = (Message)inStream.readObject();
			}
			if (message.getType() != EMessageType.TRANSMITTER_KEEPALIVE_MSG)
				lastMsgWasKeepAlive = false;
			else
				message = null;
				
		}
		Object tmpObj = message.getObject();
		if (message.getType() == EMessageType.TRANSMITTER_PACK_OF_FILES_START)
		{
			boolean stillreceiving = true;
			while(stillreceiving)
			{
				Message filemsg = (Message)receive();
				switch(filemsg.getType())
				{
					case TRANSMITTER_PACK_OF_FILES_DONE: { stillreceiving = false; break; }
					case TRANSMITTER_PACK_OF_FILES_FILE_TRANSFER:
					{
						//receiveFile((FileObject)filemsg.getObject());
						break;
					}
					default: { throw new IOException(); } //error 
				}
			}
			message.setType((EMessageType)message.getObject()); //message.getObject()==old type
			message.setObject(null);
		}
		/* check if message contains a file */
		if (tmpObj instanceof FileObject)
		{
			FileObject fileToReceive = (FileObject) tmpObj;
			File retFile = receiveFile(fileToReceive);
			message.setObject(retFile);
		}
		return message;
	}

	
	/**
	*	sends a 'keep-alive' message.
	*/
	private void sendKeepAlive() throws IOException
	{
		objectToSocket(new Message(EMessageType.TRANSMITTER_KEEPALIVE_MSG,null));
	}
	
	/**
	* Sends obj via m_Socket
	*
	* @param obj - Object
	*/
	private void objectToSocket(Object obj) throws IOException
	{
		ObjectOutputStream outStream = new ObjectOutputStream(m_Socket.getOutputStream());
		outStream.writeObject(obj);
		outStream.flush();
	}
	
	
	/**
	* sends a file through the socket
	*
	* @param fileToSend - The file to send
	* @param type		- The type of message to attach to the file
	*/
	private void sendFile(File fileToSend, Message.EMessageType type) throws IOException
	{
		String relativePath = FileObject.getRelativePathFromRoot(m_WorkingDirectory, fileToSend.getPath());
		File compressedFile = ZipManager.fileToZip(fileToSend, m_WorkingDirectory, relativePath,false);
		FileObject tmpFileObj = new FileObject(compressedFile,relativePath);
		compressedFile.delete();
		objectToSocket(new Message(type,tmpFileObj));
	}
	
	/**
	* receives a file through the socket
	*
	* @param fileToReceive - The OBJECT that contains the data of the file
	*
	* @return A File that points to the file object
	*/
	private File receiveFile(FileObject fileToReceive) throws IOException
	{
		String outputDirectory = m_WorkingDirectory + fileToReceive.getRelativePath()+ "\\";
		fileToReceive.writeFile(m_WorkingDirectory);
		File compressedFile = new File(outputDirectory + fileToReceive.fileName());
		ZipManager.zipToFile(compressedFile, outputDirectory);
		StringBuilder tmpFileName = new StringBuilder(compressedFile.getPath());
		String fileName = tmpFileName.substring(0, tmpFileName.lastIndexOf(".zip"));
		compressedFile.delete();
		return new File(fileName);
	}
}
